using System;
using System.IO;
using System.Security.Cryptography;
using System.Collections;

namespace CardsEngine.TestKit
{
	public class BasicShuffler: IShuffler
	{
        public static readonly BasicShuffler Default;

        static BasicShuffler()
        {
            Default = new BasicShuffler();
        }

		public enum RandomNumberGeneratorEngine { SYSTEM, SYSTEM_SECURITY_CRYPTOGRAPHY }

		const int DEFAULT_SWAPS = 30;
		const int DEFAULT_RUN_THROUGHS = 10;
		const RandomNumberGeneratorEngine DEFAULT_RANDOM_MODE = RandomNumberGeneratorEngine.SYSTEM;
		const int MIN_ELEMS_REQD = 2;

		// Length of the byte buffer to fill when using the cryptographic RNG
		private const int CRYPTOGRAPHIC_RNG_BUFFER_LENGTH = 64;

		public int swaps;
		public int run_throughs;

		private RandomNumberGeneratorEngine randMode;
		private object rng;
		private byte[] cryptoRNGBuf;		// Only used by the crypto-RNG

		private bool debug = false;
		private TextWriter debugStream = Console.Out;

		public BasicShuffler ()
		{
			swaps = DEFAULT_SWAPS;
			run_throughs = DEFAULT_RUN_THROUGHS;
			randMode = DEFAULT_RANDOM_MODE;
		}

		public BasicShuffler (RandomNumberGeneratorEngine _randMode)
		{
			swaps = DEFAULT_SWAPS;
			run_throughs = DEFAULT_RUN_THROUGHS;
			randMode = _randMode;
		}

		public BasicShuffler (int num_swaps, int num_run_throughs)
		{
			swaps = num_swaps;
			run_throughs = num_run_throughs;
			randMode = DEFAULT_RANDOM_MODE;
		}

		public BasicShuffler (int num_swaps, int num_run_throughs, RandomNumberGeneratorEngine _randMode)
		{
			swaps = num_swaps;
			run_throughs = num_run_throughs;
			randMode = _randMode;
		}

		/// <summary>
		/// A value of 'true' enables debug output for this instance
		/// </summary>
		public bool Debug {
			get { return debug; }
			set { debug = value; }
		}

		/// <summary>
		/// (Write-Only) set the stream for this instance's debug output,
		/// only applies if Debug is set to true
		/// </summary>
		/// <value>
		/// The debug output stream.
		/// </value>
		public TextWriter DebugStream
		{
			set { debugStream = value; }
		}

		/// <summary>
		/// Gets or sets the number of random swaps performed in a shuffle
		/// operation. In a random swap, two indices are obtained from the
		/// random number generator, and the cards at these two indices are
		/// swapped with each other.
		/// </summary>
		/// <value>
		/// The indices of the cards to swap.
		/// </value>
		public int Swaps {
			get { return swaps; }
			set { swaps = value; }
		}

		#region Public Read Accessors

		public RandomNumberGeneratorEngine RandomNumberGenerator {
			get { return randMode; }
			set {
				if (value != randMode)
				{
					randMode = value;
					MkRNG();
				}
			}
		}

		#endregion

		#region Protected Read Accessors

		protected object RandomNumberGeneratorInstance { get { return rng; } }

		#endregion

		#region Private Methods

		private void MkRNG (int seed)
		{
			switch (RandomNumberGenerator) {
			case RandomNumberGeneratorEngine.SYSTEM:
				rng = new Random (seed);
				break;

			case RandomNumberGeneratorEngine.SYSTEM_SECURITY_CRYPTOGRAPHY:
				rng = new RNGCryptoServiceProvider ();
				cryptoRNGBuf = new byte[CRYPTOGRAPHIC_RNG_BUFFER_LENGTH];
				break;

			default:
				throw new NotSupportedException ();
			}
		}

		private void MkRNG ()
		{
			MkRNG(System.Environment.TickCount);
		}

		#endregion

		#region Protected Methods

		/// <summary>
		/// Get the next random Int, using the appropriate
		/// RNG/PRNG engine associated with this instance
		/// </summary>
		/// <returns>
		/// A random integer in the range [0, INT_MAX]
		/// </returns>
		protected int NextRandomInt ()
		{
			Random stdRand;
			RNGCryptoServiceProvider cryptoRand;

			switch (RandomNumberGenerator) {
			case RandomNumberGeneratorEngine.SYSTEM:
				stdRand = rng as Random;
				return stdRand.Next();

			case RandomNumberGeneratorEngine.SYSTEM_SECURITY_CRYPTOGRAPHY:
				cryptoRand = rng as RNGCryptoServiceProvider;
				cryptoRand.GetBytes(cryptoRNGBuf);
				return BitConverter.ToInt32 (cryptoRNGBuf,0);

			default:
				throw new NotSupportedException();
			}


		}

		#endregion

		#region IShuffler Members

		public void Shuffle (IEnumerable deck)
		{
			IList deck_l = deck as IList;

			if (deck_l == null)
				throw new ArgumentException("This shuffler requires that the 'deck' parameter implement IList");

			Shuffle (deck_l, System.Environment.TickCount);
		}

		#endregion

		private void DebugWrite (string msg, params object[] arg)
		{
			if (debug)
				debugStream.Write(msg, arg);
		}

		private void DebugWriteLine ()
		{
			if (debug)
				debugStream.WriteLine ();
		}

		private void DebugWriteLine (string msg, params object[] arg)
		{
			if (debug)
				debugStream.WriteLine (msg, arg);
		}

		public void Shuffle (IList deck, int randomSeed)
		{
			// Save time by performing only one check instead of
			// multiple calls to DebugWriteLine
			if (debug) {
				debugStream.WriteLine ("Debugging call to BasicShuffler.Shuffle");
				debugStream.WriteLine ("------------INSTANCE DATA--------------");
				debugStream.WriteLine ("swaps=\t{0}\nrun_throughs=\t{1}", swaps, run_throughs);
				debugStream.WriteLine ("--------------CONSTANTS----------------");
				debugStream.WriteLine ("MIN_ELEMS_REQD=\t{0}", MIN_ELEMS_REQD);
				debugStream.WriteLine ("-------------PARAMETERS----------------");
				debugStream.WriteLine ("deck=\t{0}\nrandomSeed=\t{1}\ndebug=\t{2}", deck.ToString (), randomSeed, debug);
				debugStream.WriteLine ("---------------------------------------");
			}

			DebugWrite ("Initializing local variables... ");


			object tmp;
			int swap1, swap2, numCards = deck.Count;

			DebugWrite ("Done\nVerifying minimum number of elements present in collection... ");

			if (numCards < MIN_ELEMS_REQD)
				throw new InvalidOperationException (string.Format (
					"Cannot shuffle a deck containing less than {0} cards",
					MIN_ELEMS_REQD));

			DebugWriteLine ("Done.\nPerforming {0} run throughs...", run_throughs);

			// Iterate through the entire deck "run_throughs" times,
			// each time placing each card at a random position
			for (int j=0; j<run_throughs; j++) {
				DebugWriteLine ("\tBegin Run-through No. {0}", j);
				for (int k=0; k<numCards; k++) {
					DebugWrite("\t\tSwapping cards at indices {0} and ",k);
					do {
						swap1 = NextRandomInt () % numCards;
					} while (swap1 == k);
					DebugWrite("{0}... ",swap1);

					tmp = deck [k];
					deck [k] = deck [swap1];
					deck [swap1] = tmp;

					DebugWriteLine ("Done");
				}

				DebugWriteLine ("\tEnd Run-through No. {0}", j);
			}

			DebugWriteLine ("Done with run throughs.\nPerforming {0} random swaps...");

			// Perform "swaps" number of swaps by choosing two random cards
			for (int j=0; j<swaps; j++) {
				DebugWrite("\t Swapping cards at indices ");
				swap1 = NextRandomInt () % numCards;
				DebugWrite("{0} and ",swap1);
				do {
					swap2 = NextRandomInt () % numCards;
				} while (swap1 == swap2);
				DebugWrite("{0}... ",swap2);

				tmp = deck [swap1];
				deck [swap1] = deck [swap2];
				deck [swap2] = deck [swap1];

				DebugWriteLine ("Done");
			}
			DebugWriteLine("Done with random swaps");

			DebugWriteLine("\nFinished");
		}
	}
}

